package org.egrit;

import java.util.Set;

import org.reflections.Reflections;

/**
 * Provides static methods to create grids.
 *
 * @author michael
 */
public class Grids {

    private static final Reflections lookup = new Reflections("org.egrit");
    
    /**
     * Creates a new constant grid.
     * 
     * @param width grid width
     * @param height grid height
     * @param constantValue grid constant value
     * @param outsideValue grid outside value
     * 
     * @return a new grid
     */
    public static IGrid createConstant(int width, int height, 
            Number constantValue, Number outsideValue) {
        
        return new ConstantGrid(width, height, constantValue, outsideValue);
    }
    
    /**
     * Creates a new constant grid.
     * 
     * @param bounds grid bounds
     * @param constantValue grid constant value
     * @param outsideValue grid outside value
     * 
     * @return a new grid
     */
    public static IGrid createConstant(IGridBounds bounds, 
            Number constantValue, Number outsideValue) {
        
        return new ConstantGrid(bounds, constantValue, outsideValue);
    }
    
    /**
     * Creates a new calculated grid.
     * 
     * @param width grid width
     * @param height grid height
     * @param valueFn function providing grid values
     * @param outsideValue grid outside value
     * 
     * @return a new grid
     */
    public static IGrid createCalculated(int width, int height, 
            GridFunction valueFn, Number outsideValue) {
        
        return new CalculatedGrid(width, height, valueFn, outsideValue);
    }
    
    /**
     * Creates a new calculated grid.
     * 
     * @param bounds grid bounds
     * @param valueFn function providing grid values
     * @param outsideValue grid outside value
     * 
     * @return a new grid
     */
    public static IGrid createCalculated(IGridBounds bounds, 
            GridFunction valueFn, Number outsideValue) {
        
        return new CalculatedGrid(bounds, valueFn, outsideValue);
    }
    
    /**
     * Creates a new grid based on data from a source grid. The bounds
     * argument specifies the area of the source grid to copy and is also
     * used as the bounds of the new grid. The bounds are allowed to
     * extended beyond those of the source grid.
     * 
     * @param source the source grid
     * @param bounds bounds of data to copy
     * 
     * @return a new grid
     */
    public static IGrid create(IGrid source, IGridBounds bounds) {
        Set<Class<? extends IGridFactory>> factoryClasses =
                lookup.getSubTypesOf(IGridFactory.class);
        
        if (factoryClasses.isEmpty()) {
            throw new UnsupportedOperationException("No suitable grid factory found");
        }
        
        // *** TODO choose factory based on some user parameters ***
        
        try {
            Class<? extends IGridFactory> clazz = factoryClasses.iterator().next();
            IGridFactory factory = clazz.newInstance();
            return factory.create(source, bounds);
            
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
            
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        }
    }
    
    /**
     * Creates a new writable grid.
     * 
     * @param width
     * @param height
     * @param fillValue
     * @param outsideValue
     * @return 
     */
    public static IWritableGrid createWritable(int width, int height, 
            Number fillValue, Number outsideValue) {
        
        return createWritable(
                GridBoundsFactory.create(0, width - 1, 0, height - 1),
                fillValue, outsideValue);
    }
    
    public static IWritableGrid createWritable(IGridBounds bounds, 
            Number fillValue, Number outsideValue) {

        Set<Class<? extends IWritableGridFactory>> factoryClasses =
                lookup.getSubTypesOf(IWritableGridFactory.class);
        
        if (factoryClasses.isEmpty()) {
            throw new UnsupportedOperationException("No suitable grid factory found");
        }
        
        // *** TODO choose factory based on some user parameters ***
        
        try {
            Class<? extends IWritableGridFactory> clazz = factoryClasses.iterator().next();
            IWritableGridFactory factory = clazz.newInstance();
            return factory.createWritable(bounds, fillValue, outsideValue);
            
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
            
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        }
    }
    
}
